/*
* ============================================================================
*  Name        : sea_iterator.h
*  Part of     : Finite state machine utilities
*  Description : Object iterator
*  Version     : %version: %
*  
*
* ============================================================================
*/
/**
 * @file sea_iterator.h
 * @brief 
 * Vertical @ref sea::VIterator and Horozontal @ref sea::HIterator among associations of an object
 * 
 * @ingroup AssociationUtilities
 */
#if !defined( __sea_iterator_h__ )
#define __sea_iterator_h__

#include "sea_list.h"
#include "sea_link.h"

namespace sea
{
    /** 
     * @class HIterator sea_iterator.h "sea_iterator.h"
     * @brief
     *      Implements \b Horizontal iteration( directional ) among objects by specified association
     * @ingroup AssociationUtilities
     */
    template< typename T = Object, Direction DIRECTION = Right >
    class HIterator 
    {
            friend class List;

        public:
            typedef sea::ref< > ( Object::*GetObjMethod )( Association id );

            HIterator( sea::aref& obj, Association id = -1 ) :
                m_direction( DIRECTION )
            {
                if ( obj.as< sea::List >( ) )
                {
                    m_id = obj.as< sea::List >( )->id( );

                    if ( m_direction == Right ) 
                    {
                        m_id.reset( );
                        m_obj = obj.as< sea::List >( )->get_first( ).get( );
                        m_get_obj_method = &sea::Object::get_right_obj;
                    }
                    else 
                    {
                        m_id.reset( obj.as< sea::List >( )->size( ) - 1 );
                        m_obj = obj.as< sea::List >( )->get_last( ).get( );
                        m_get_obj_method = &sea::Object::get_left_obj;
                    }
                }
                else
                {
                    m_id = id;

                    m_obj = obj.as< T >( );

                    if ( m_direction == Right ) 
                    {
                        m_get_obj_method = &sea::Object::get_right_obj;
                    }
                    else 
                    {
                        m_get_obj_method = &sea::Object::get_left_obj;
                    }
                }
                ASSERT_DEBUG( m_obj );
            }

            HIterator( sea::ref< T >& obj, Association id = -1 ) :
                m_id( id ),
                m_obj( obj ),
                m_direction( DIRECTION )
            {
                ASSERT_DEBUG( m_obj );

                if ( m_direction == Right ) 
                {
                    m_get_obj_method = &sea::Object::get_right_obj;
                }
                else 
                {
                    m_get_obj_method = &sea::Object::get_left_obj;
                }
            }

            HIterator( Object* obj, Association id = -1 ) :
                m_id( id ),
                m_obj( dynamic_cast< T* >( obj ) ),
                m_direction( DIRECTION )
            {
                ASSERT_DEBUG( m_obj );

                if ( m_direction == Right ) 
                {
                    m_get_obj_method = &sea::Object::get_right_obj;
                }
                else 
                {
                    m_get_obj_method = &sea::Object::get_left_obj;
                }
            }

            HIterator( List* list ) :
                m_direction( DIRECTION ) 
            { 
                ASSERT_DEBUG( list );
                m_id = list->id( );

                if ( m_direction == Right ) 
                {
                    m_id.reset( );
                    m_obj = list->get_first( ).get( );
                    m_get_obj_method = &sea::Object::get_right_obj;
                }
                else 
                {
                    m_id.reset( list->size( ) - 1 );
                    m_obj = list->get_last( ).get( );
                    m_get_obj_method = &sea::Object::get_left_obj;
                }
                ASSERT_DEBUG( m_obj );
            }

            HIterator( List& list ) :
                m_id( list.id( ) ),
                m_direction( DIRECTION ) 
            { 
                if ( m_direction == Right ) 
                {
                    m_id.reset( );
                    m_obj = list.get_first( );
                    m_get_obj_method = &sea::Object::get_right_obj;
                }
                else 
                {
                    m_id.reset( list.size( ) - 1 );
                    m_obj = list.get_last( );
                    m_get_obj_method = &sea::Object::get_left_obj;
                }
                ASSERT_DEBUG( m_obj );
            }

            T* operator*( ) { return m_obj.get( ); }
            
            T* operator->( ) { return operator*( ); }
            
            T* operator++( ) 
            { 
                m_obj = ( m_obj.get( )->*m_get_obj_method )( m_id ).get( ); 
                m_direction == Right ? m_id.next( ) : m_id.prev( );
                return operator*( ); 
            }

            T* operator++( int ) { return operator++( ); }

            T* operator--( ) { return operator++( ); } 

            T* operator--( int ) { return operator++( ); }
            
            T* advance( ){ return operator++( ); }

        private:
            Association association( ) { return m_id; }

        private:
            Association m_id;
            sea::ref< T > m_obj;
            GetObjMethod m_get_obj_method;
            Direction m_direction;
        };

    /** 
     * @class VIterator sea_iterator.h "sea_iterator.h"
     * @brief
     *      Implements \b Vertical iteration( directional ) among objects by specified association
     * @ingroup AssociationUtilities
     */
    template< typename T = Object, Direction DIRECTION = Right >
    class VIterator 
    {
            friend class List;

        public:
            typedef sea::ref< Link > ( Link::*GetLinkMethod )( );

            VIterator( sea::ref< T > obj, Association id = -1 ) :
                m_id( id ),
                m_link_association_id( 0 )
            {
                if ( DIRECTION == Right )
                {
                    m_get_link_func = &sea::Link::right_link; 
                    m_get_opposite_link_func = NULL;
                }
                else
                if ( DIRECTION == Left )
                {
                    m_get_link_func = &sea::Link::left_link;
                    m_get_opposite_link_func = NULL;
                }
                else
                {
                    m_get_link_func = &sea::Link::right_link; 
                    m_get_opposite_link_func = &sea::Link::left_link;
                }

                get_next( obj->get_link( id ) );
            }

            VIterator( Object* obj, Association id = -1 ) :
                m_id( id ),
                m_link_association_id( 0 )
            {
                if ( DIRECTION == Right )
                {
                    m_get_link_func = &sea::Link::right_link; 
                    m_get_opposite_link_func = NULL;
                }
                else
                if ( DIRECTION == Left )
                {
                    m_get_link_func = &sea::Link::left_link;
                    m_get_opposite_link_func = NULL;
                }
                else
                {
                    m_get_link_func = &sea::Link::right_link; 
                    m_get_opposite_link_func = &sea::Link::left_link;
                }

                get_next( obj->get_link( id ) );
            }

            T* operator*( ) 
            {
                Link* my_link = m_link.get( );
                if ( !my_link )
                {
                    return NULL;
                }

                sea::ref< Link > link = ( my_link->*m_get_link_func )( );
                if ( link != NULL )
                {
                    return reinterpret_cast< T* >( link->host( ).get( ) );
                }

                if ( !m_get_opposite_link_func )
                {
                    return NULL;
                }

                link = ( my_link->*m_get_opposite_link_func )( );
                if ( link == NULL )
                {
                    return NULL;
                }
                return reinterpret_cast< T* >( link->host( ).get( ) );
            }

            T* operator->( ) { return operator*( ); }

            T* operator++( ) 
            { 
                return get_next( m_link->down_link( m_link_association_id ) ); 
            }
            T* operator++( int ) { return operator++( ); }
            
            template< typename TYPE > 
            TYPE* by( ) { return m_link->get_association( ).as< TYPE >( ); }

            Association get_association( ) { return m_link->get_association( ); }

            sea::ref< Link > get_link( ) { return m_link; }

            bool move_first( )
            {
                sea::aref host( m_link->host( ) );
                sea::ref< Link > the_first( host->get_first_link( ) );
                if ( the_first != m_link )
                {
                    m_link->detach( m_link_association_id );
                    m_link->attach_left_of( the_first.get( ), m_link_association_id );

                    host->set_first_link( m_link.get( ) );
                    return true;
                }
                return false;
            }

        private:
            T* get_next( sea::ref< Link > link ) 
            {
                // Going down through obj associations
                for( m_link = link; m_link != NULL ; m_link = m_link->down_link( m_link_association_id ) )
                {
                    Link* my_link = m_link.get( );

                    if ( m_id.is_any( ) || my_link->get_association( ) == m_id ) 
                    {
                        // Take neighbour link on chosen side
                        sea::ref< Link > link = ( my_link->*m_get_link_func )( );

                        Link* n_link = link.get( );
                        if ( n_link )
                        {
                            // ...and return host
                            return reinterpret_cast< T* >( n_link->host( ).get( ) );
                        }
                        
                        if ( !m_get_opposite_link_func )
                        {
                            continue;
                        }

                        // Take opposite of m_get_link_func
                        link = ( my_link->*m_get_opposite_link_func )( );

                        n_link = link.get( );
                        if ( n_link )
                        {
                            // ...and return host
                            return reinterpret_cast< T* >( n_link->host( ).get( ) );
                        }
                    }
                }
                return NULL;
            }

        private:
            sea::ref< Link > m_link;                   ///< Current link in vertical search
            Association m_id;                          ///< Association id for the search
            Association m_link_association_id;         ///< Association id used for connecting links themselves.This is Assiciation( 0 ). Performance helper.
            GetLinkMethod m_get_link_func;             ///< Either Right or Left objects are checked
            GetLinkMethod m_get_opposite_link_func;    ///< When Either direction is chosen, it has opposite direction of m_get_link
        };
}

#endif // #if !defined( __sea_iterator_h__ )
